import { Token } from "./token";

abstract class Lexer {
    static EOF = String.fromCharCode(-1);
    static EOF_TYPE = 1;

    input: string;
    p: number = 0;
    c: string;

    constructor(input: string) {
        this.input = input;
        this.c = this.input.charAt(this.p);
    }

    consume() {
        this.p = this.p + 1;
        if (this.p >= this.input.length) {
            this.c = Lexer.EOF;
        } else {
            this.c = this.input.charAt(this.p)
        }
    }

    /**
     * 预测下一个
     * @param x 
     * @returns 
     */
    match(x: string): boolean {
        return this.c == x;
    }
    accept(x: string): boolean {
        if (this.c != x) {
            console.log("expecting ", x);
            console.log("found ", this.c);
            throw new Error("lexer accept error")
        }
        return this.c == x;
    }

    abstract nextToken(): Token
    abstract getTokenName(tokenType: number): string

}
enum LexerMode {
    DEFAULT_MODE,
    JSX_MODE,
    HTML_MODE,
    TAG_MODE
}

class JSXLexer extends Lexer {
    // https://github.com/microsoft/TypeScript/blob/main/src/compiler/scanner.ts#L176
    // <
    static LT = 2
    // >
    static GT = 3
    // {
    static LP = 4
    // }
    static RP = 5
    // ' SINGLE_QUOTE
    static SQ = 6
    // " DOUBLE_QUOTE
    static DQ = 7
    // =
    static EQ = 8
    // / separator
    static SE = 9;

    static NAME = 10
    static CODE = 11;
    static STRING = 12;
    static TEXT = 13;

    static tokenNames = [
        'n/a',
        '<EOF>',
        '<',
        '>',
        '{',
        '}',
        "'",
        '"', // 7
        '=',
        '/',
        "Name",
        "Code",
        "String",
        "TEXT"  // 12
    ]

    tokens: Token[] = [];

    modeStack: number[] = [LexerMode.DEFAULT_MODE]

    pushMode(mode: LexerMode) {
        this.modeStack.push(mode);
    }
    popMode() {
        this.modeStack.pop();
    }
    currentMode() {
        return this.modeStack[this.modeStack.length - 1];
    }
    jsxLevel = 1;

    nextToken(): Token {
        //切换模式
        while (this.c != JSXLexer.EOF) {
            let tempMode = this.currentMode();

            if (tempMode == LexerMode.DEFAULT_MODE) {
                switch (this.c) {
                    case ' ': case '\t': case '\n': case '\r':
                        this.WS();
                        break;
                    case '<':
                        this.tokens.push(new Token(JSXLexer.LT, '<'))
                        this.consume();

                        this.TAG_OPEN();

                        this.pushMode(LexerMode.HTML_MODE);
                        this.pushMode(LexerMode.TAG_MODE);

                        break;
                    default:
                        this.JS()
                        break;
                }
            } else if (tempMode == LexerMode.TAG_MODE) {
                switch (this.c) {
                    case ' ': case '\t': case '\n': case '\r':
                        this.WS();
                        break;
                    case '>':
                        // 开始标签结束
                        this.tokens.push(new Token(JSXLexer.GT, '>'))
                        this.consume();
                        // 进入html内容解析
                        this.popMode();
                        break;
                    case '/':
                        // 单闭合标签
                        this.tokens.push(new Token(JSXLexer.SE, '/'))
                        this.consume();

                        this.HWS();
                        // 判断是否为`/>`
                        if (this.accept('>')) {
                            this.tokens.push(new Token(JSXLexer.GT, '>'));
                            this.consume();
                            
                            // 进入默认模式
                            this.popMode()
                            this.popMode();
                        }

                        break;
                    case '=':
                        // =
                        this.tokens.push(new Token(JSXLexer.EQ, "="))
                        this.consume();

                        break;
                    case '\'':
                        this.tokens.push(new Token(JSXLexer.SQ, '\''))
                        this.consume();
                        this.SINGLE_QUOTE_STRING();
                        break;
                    case '"':
                        this.tokens.push(new Token(JSXLexer.DQ, '"'))
                        this.consume();
                        this.DOUBLE_QUOTE_STRING();
                        break;
                    case '{':
                        // 开始jsx解析
                        this.tokens.push(new Token(JSXLexer.LP, "{"))
                        this.consume();

                        this.pushMode(LexerMode.JSX_MODE);

                        break;
                    default:
                        //尝试匹配属性名称
                        this.TAG_NAME();
                }
            } else if (tempMode == LexerMode.HTML_MODE) {
                switch (this.c) {
                    case ' ': case '\t': case '\n': case '\r':
                        //html的空白内容保留
                        this.HTML_WS();
                        break;
                    case '<':
                        this.tokens.push(new Token(JSXLexer.LT, '<'))
                        this.consume();

                        // 检查是否闭合标签
                        this.HWS();
                        if (this.match('/')) { //闭合标签

                            this.tokens.push(new Token(JSXLexer.SE, '/'))
                            this.consume();

                            this.HTML_CLOSE();

                            //进入js模式或者jsx模式
                            this.popMode();

                        } else {  //嵌套标签
                            this.HTML_TAG_OPEN();

                            //进入标签模式
                            this.pushMode(LexerMode.HTML_MODE);
                            this.pushMode(LexerMode.TAG_MODE);

                        }
                        break;
                    case '{':
                        // 开始jsx解析
                        this.tokens.push(new Token(JSXLexer.LP, "{"))
                        this.consume();

                        this.pushMode(LexerMode.JSX_MODE);

                        break;
                    default:
                        // 文本内容
                        this.HTML_TEXT();

                }
            } else if(tempMode == LexerMode.JSX_MODE){
                switch(this.c){
                    case ' ': case '\t': case '\n': case '\r':
                        this.WS();
                        break;
                    case '<':
                        this.tokens.push(new Token(JSXLexer.LT, '<'))
                        this.consume();

                        this.TAG_OPEN();

                        this.pushMode(LexerMode.HTML_MODE);
                        this.pushMode(LexerMode.TAG_MODE);

                        break;
                    default:
                        this.JSX_CODE();
                        this.popMode();
                        break;
                }
            }
        }
        return new Token(JSXLexer.EOF_TYPE, "<EOF>");
    }
    // 默认js模式
    TAG_OPEN() {
        this.HWS();
        let nameResult = this.Name();
        if (nameResult[0]) {
            //生成name Token
            this.tokens.push(new Token(JSXLexer.NAME, nameResult[1]));
        }
    }
    JS() {
        let jsCode = '';
        while (this.c != JSXLexer.EOF) {
            if(this.c == '<'){
                //找到标签
                break;
            }
            jsCode = jsCode + this.c;
            this.consume();
        }
        this.tokens.push(new Token(JSXLexer.CODE, jsCode));
    }
    // html模式 
    HTML_TAG_OPEN() {
        // 匹配标签名称
        this.HWS();
        let nameResult = this.Name();
        if (nameResult[0]) {
            //生成name Token
            this.tokens.push(new Token(JSXLexer.NAME, nameResult[1]));
        }
    }
    HTML_CLOSE() {
        //匹配Name>
        let nameResult = this.Name();
        if (nameResult[0] && this.accept('>')) {
            this.tokens.push(new Token(JSXLexer.NAME, nameResult[1]));
            this.tokens.push(new Token(JSXLexer.GT,'>'));
            this.consume();
        }
    }
    HTML_TEXT() {
        let text = '';
        while(this.c != JSXLexer.EOF){
            if(this.match('{') || this.match('<')){
                break;
            }else{
                text = text + this.c;
                this.consume();
            }
        }
        this.tokens.push(new Token(JSXLexer.TEXT, text));
    }
    // html空白保留
    HTML_WS() {
        let buf = '';
        let whiteSpace = [' ', "\t", "\n", '\r'];

        while (whiteSpace.indexOf(this.c) > -1) {
            buf = buf + this.c;
            this.consume();
        }
        
        this.tokens.push(new Token(JSXLexer.TEXT, buf));
    }

    // Tag开始标签解析
    TAG_NAME() {
        let nameResult = this.Name();
        if (nameResult[0]) {
            //生成name Token
            this.tokens.push(new Token(JSXLexer.NAME, nameResult[1]));
        }
    }
    
    // fragment DOUBLE_QUOTE_STRING: '"' ~["]* '"';
    DOUBLE_QUOTE_STRING() {
        let success = false;
        let buf = '';
        while (this.c != '"') {
            if (this.c == Lexer.EOF) {
                break;
            }
            buf = buf + this.c;
            this.consume();
        }
        success = this.c == '"' ? true : false;
        if (success) {
            this.tokens.push(new Token(JSXLexer.STRING, buf))
        }

        //压入结束的标记
        this.tokens.push(new Token(JSXLexer.DQ, '"'))
        this.consume();
    }

    // fragment SINGLE_QUOTE_STRING: '\'' ~[']* '\'';
    SINGLE_QUOTE_STRING() {
        let success = false;
        let buf = '';
        while (this.c != '\'') {
            if (this.c == Lexer.EOF) {
                break;
            }
            buf = buf + this.c;
            this.consume();
        }
        success = this.c == '\'' ? true : false;
        if (success) {
            this.tokens.push(new Token(JSXLexer.STRING, buf))
        }

        this.tokens.push(new Token(JSXLexer.SQ, '\''))
        this.consume();
    }
    // jsx模式
    JSX_CODE(){
        //默认大括号层数
        let code = '';
        while(this.c != JSXLexer.EOF){
            //检查是否< 直接跳出切换到标签模式 
            // {}中不支持<开头的运算符< <=
            // 全部替换成> >=
            if(this.match('<')){
                break;
            }
            if(this.match('}')){
                this.jsxLevel = this.jsxLevel - 1;
            }else{
                if(this.match('{')){
                    this.jsxLevel =  this.jsxLevel + 1;
                }
                code = code + this.c;
            }
            if( this.jsxLevel == 0){
                break;
            }
            this.consume();
        }
        //压入代码 可能代码未结束
        this.tokens.push(new Token(JSXLexer.CODE,code));
        //压入结束标记}
        if(this.c.match("}")){
            this.tokens.push(new Token(JSXLexer.RP,'}'))
            this.consume();
        }
    }

    // fragment NAME : NameStartChar NameChar*;
    Name(): [boolean, string] {
        let succss = true;
        let buf = '';

        if (this.TAG_NameStartChar()) {
            buf = buf + this.c;
            this.consume();
            while (this.TAG_NameChar()) {
                buf = buf + this.c;
                this.consume();
            }
        } else {
            succss = false
        }
        return [succss, buf];
    }
    // fragment TAG_NameChar:
    TAG_NameChar(): boolean {
        return this.TAG_NameStartChar()
            || this.c == '-'
            || this.c == '_'
            || this.c == '.'
            || this.DIGIT()
    }
    // fragment TAG_NameStartChar:
    TAG_NameStartChar(): boolean {
        return (this.c >= 'a' && this.c <= 'z')
            || (this.c >= 'A' && this.c <= 'Z')
    }
    // fragment DIGIT: [0-9];
    DIGIT(): boolean {
        return Number.isInteger(this.c) && parseInt(this.c) >= 0 && parseInt(this.c) <= 9;
    }
    WS() {
        let whiteSpace = [' ', "\t", "\n", '\r'];

        while (whiteSpace.indexOf(this.c) > -1) {
            this.consume();
        }
    }
    HWS() {
        let whiteSpace = [' ', "\t"];
        while (whiteSpace.indexOf(this.c) > -1) {
            this.consume();
        }
    }

    getTokenName(tokenType: number): string {
        return JSXLexer.tokenNames[tokenType];
    }

    toString(){
        let buf = `tokens-----------\n`;
        this.tokens.forEach(t => {
            buf+= `[${this.getTokenName(t.type)}]:'${t.text}'\n`
        })
        buf += `\ntokens-----------`
        return buf;
    }
}

export {
    Lexer,
    JSXLexer
}